#pragma once

#include "json.hpp"
#include "mqttClient.hpp"
#include "sql.hpp"

class SqlManager {
public:
    using json = nlohmann::json;

private:
    Sqlite3 *_sql;
    MqttClient *_mqtt;

private:
    const std::string WRITE_SQL = "dev/+/dev_sqlite/write";
    const std::string READ_SQL = "dev/+/dev_sqlite/read";
    const std::string DELETE_SQL = "dev/+/dev_sqlite/delete";
    const std::string APP_READ_TOPIC = "app/+/dev_sqlite/read";
    const std::string selfSign = "dev_sqlite";

private:
    std::vector<std::string> split(const std::string &str, const std::string &pattern);
    std::string getDate(void);

private:
    void waitMqttMsgThread(void);

private:
    bool parseMqttMessage(const MqttClient::Msg_t &msg);
    bool sqlReadMsgParse(const MqttClient::Msg_t &msg);
    bool sqlReadMsgResponse(const MqttClient::Msg_t &msg);

    bool sqlWriteMsgParse(const MqttClient::Msg_t &msg);
    bool sqlWriteMsgResponse(const MqttClient::Msg_t &msg, bool result);

    bool appReadMsgResponse(const MqttClient::Msg_t &msg);

    bool sqlDeleteMsgParse(const MqttClient::Msg_t &msg);
    bool sqlDeleteMsgResponse(int tick, bool result, const std::string &topic);

public:
    SqlManager();
    ~SqlManager();
};

SqlManager::SqlManager()
    : _sql(new Sqlite3())
    , _mqtt(new MqttClient("dev_sqlite"))
{
    _mqtt->connect("localhost");
    _mqtt->registerOnConnected(std::bind(&MqttClient::subscribe, _mqtt, WRITE_SQL));
    _mqtt->registerOnConnected(std::bind(&MqttClient::subscribe, _mqtt, READ_SQL));
    _mqtt->registerOnConnected(std::bind(&MqttClient::subscribe, _mqtt, APP_READ_TOPIC));
    _mqtt->registerOnConnected(std::bind(&MqttClient::subscribe, _mqtt, DELETE_SQL));

    std::thread(&SqlManager::waitMqttMsgThread, this).detach();
}

std::vector<std::string> SqlManager::split(const std::string &str, const std::string &pattern)
{
    std::vector<std::string> result;
    std::string::size_type pos = 0;
    std::string::size_type prev = 0;

    while ((pos = str.find(pattern, prev)) != std::string::npos) {
        auto r = str.substr(prev, pos - prev);

        r.size() ? result.push_back(r) : (void)(nullptr);

        prev = pos + pattern.length();
    }
    auto r = str.substr(prev);
    r.size() ? result.push_back(r) : (void)(nullptr);

    return result;
}

std::string SqlManager::getDate(void)
{
    auto time = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());

    std::ostringstream oss;
    oss << std::put_time(std::localtime(&time), "%Y-%m-%d");
    return oss.str();
}

bool SqlManager::sqlReadMsgResponse(const MqttClient::Msg_t &msg)
{
    json recv = json::parse(msg.payload);

    json root;
    root["tick"] = recv["tick"];
    root["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["src"] = selfSign;
    root["ack"]["tick"] = recv["tick"];
    root["ack"]["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["ack"]["status"] = "success";

    auto result = _sql->selectAll();

    for (const auto &item : result) {
        json record;
        record["testMode"] = item.testMode;
        record["testType"] = item.testType;
        record["testDate"] = item.testDate;
        record["testPhase"] = item.testPhase;
        record["testCurrent"] = item.testCurrent;
        record["dropTime"] = item.dropTime;
        record["dropCause"] = item.dropCause;
        record["iSpass"] = item.isPass;

        root["record"].push_back(record);
    }

    auto topic = split(msg.topic, "/");

    return _mqtt->publish("dev/" + topic[2] + "/" + topic[1] + "/rack", root.dump());
}

bool SqlManager::sqlReadMsgParse(const MqttClient::Msg_t &msg)
{
    return sqlReadMsgResponse(msg);
}

bool SqlManager::sqlWriteMsgResponse(const MqttClient::Msg_t &msg, bool result)
{
    json recv = json::parse(msg.payload);

    json root;
    root["tick"] = recv["tick"];
    root["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["src"] = selfSign;
    root["ack"]["tick"] = recv["tick"];
    root["ack"]["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["ack"]["status"] = "success";
    if (result) {
        root["status"] = "success";
    } else {
        root["status"] = "failed";
    }

    auto topic = split(msg.topic, "/");

    return _mqtt->publish("dev/" + topic[2] + "/" + topic[1] + "/wack", root.dump());
}

bool SqlManager::sqlWriteMsgParse(const MqttClient::Msg_t &msg)
{
    json root = json::parse(msg.payload);

    bool result = false;

    for (const auto &record : root["record"]) {
        const int testMode = record["testMode"].get<int>();
        const int testType = record["testType"].get<int>();
        const int testDate = record["testDate"].get<int>();
        const int testPhase = record["testPhase"].get<int>();
        const int testCurrent = record["testCurrent"].get<int>();
        const double dropTime = record["dropTime"].get<double>();
        const int dropCause = record["dropCause"].get<int>();
        const int isPass = record["iSpass"].get<int>();

        result = _sql->insert(testMode, testType, testDate, testPhase, testCurrent, dropTime, dropCause, isPass);
    }

    return sqlWriteMsgResponse(msg, result);
}

bool SqlManager::appReadMsgResponse(const MqttClient::Msg_t &msg)
{
    json root;

    root["tick"] = 1;
    root["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["src"] = selfSign;
    root["ack"]["tick"] = 1;
    root["ack"]["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["ack"]["status"] = "success";

    json item;
    item["key"] = "log";
    item["val"] = "i am " + selfSign;
    root["app"]["points"].push_back(item);

    item["key"] = "version";
    item["val"] = DEV_SQLITE_VERSION;
    root["app"]["points"].push_back(item);

    item["key"] = "date";
    item["val"] = getDate();
    root["app"]["points"].push_back(item);

    auto topic = split(msg.topic, "/");

    return _mqtt->publish(topic[0] + "/" + topic[2] + "/" + topic[1] + "/rack", root.dump());
}

bool SqlManager::sqlDeleteMsgResponse(int tick, bool result, const std::string &topic)
{
    json root;
    root["tick"] = tick;
    root["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["src"] = selfSign;
    root["ack"]["tick"] = tick;
    root["ack"]["time"] = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    root["ack"]["status"] = "success";

    if (result) {
        root["status"] = "success";
    } else {
        root["status"] = "failed";
    }

    auto ntopic = split(topic, "/");

    return _mqtt->publish(ntopic[0] + "/" + ntopic[2] + "/" + ntopic[1] + "/dack", root.dump());
}

bool SqlManager::sqlDeleteMsgParse(const MqttClient::Msg_t &msg)
{
    json root = json::parse(msg.payload);

    const int tick = root["tick"].get<int>();

    std::vector<uint64_t> dateVec;

    for (const auto &item : root["record"]) {
        auto testDate = item["testDate"].get<int>();

        std::cout << "prepare delete testDate : " << testDate << std::endl;

        _sql->drop(testDate);
    }
    return sqlDeleteMsgResponse(tick, true, msg.topic);
}

bool SqlManager::parseMqttMessage(const MqttClient::Msg_t &msg)
{
    if (msg.topic.find("dev/") != std::string::npos && msg.topic.find("/read") != std::string::npos) {
        return sqlReadMsgParse(msg);
    } else if (msg.topic.find("dev") != std::string::npos && msg.topic.find("/write") != std::string::npos) {
        return sqlWriteMsgParse(msg);
    } else if (msg.topic.find("app") != std::string::npos && msg.topic.find("/read") != std::string::npos) {
        return appReadMsgResponse(msg);
    } else if (msg.topic.find("delete") != std::string::npos) {
        return sqlDeleteMsgParse(msg);
    }

    return false;
}

void SqlManager::waitMqttMsgThread(void)
{
    while (true) {
        auto msg = _mqtt->getOneItem();
        parseMqttMessage(msg);
    }
}

SqlManager::~SqlManager()
{
    if (_sql) {
        delete _sql;
    }

    if (_mqtt) {
        delete _mqtt;
    }
}
